﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace IntegratedOnBordDebug.UDPSocket
{
    public sealed unsafe class WinsockSockAddr
    {
        const Int16 AF_INET = 2;
        const Int16 AF_INET6 = 23;

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        internal struct SOCKADDR_IN
        {
            public Int16 _family;
            public Int16 _port;
            public Byte _addr0;
            public Byte _addr1;
            public Byte _addr2;
            public Byte _addr3;
            public Int32 _nothing;
        }

        static readonly int SIZEOF_SOCKADDR_IN = Marshal.SizeOf(typeof(SOCKADDR_IN));

        [StructLayout(LayoutKind.Sequential)]
        internal struct SOCKADDR_IN6
        {
            public Int16 _family;
            public Int16 _port;
            public Int32 _flowInfo;
            public Byte _addr0;
            public Byte _addr1;
            public Byte _addr2;
            public Byte _addr3;
            public Byte _addr4;
            public Byte _addr5;
            public Byte _addr6;
            public Byte _addr7;
            public Byte _addr8;
            public Byte _addr9;
            public Byte _addr10;
            public Byte _addr11;
            public Byte _addr12;
            public Byte _addr13;
            public Byte _addr14;
            public Byte _addr15;
            public Int32 _scopeID;
        }

        static readonly int SIZEOF_SOCKADDR_IN6 = Marshal.SizeOf(typeof(SOCKADDR_IN6));

        // Depending on the family type of address represented, either a SOCKADDR_IN
        // or a SOCKADDR_IN6 will be referenced by _addr.  We'll pin the same object
        // to _pinAddr, and finally keep a IntPtr to the alloc.
        object _addr;
        GCHandle _pinAddr;
        IntPtr _pAddr;

        public WinsockSockAddr(IPEndPoint source)
            : this(source.Address, (short)source.Port)
        {
        }

        public WinsockSockAddr(IPAddress source)
            : this(source, 0)
        {
        }

        public WinsockSockAddr(IPAddress source, short port)
        {
            _pAddr = (IntPtr)0;

            if (source.AddressFamily == AddressFamily.InterNetwork)
            {
                SOCKADDR_IN a;
                Byte[] addr = source.GetAddressBytes();
                Debug.Assert(addr.Length == 4);

                a._family = AF_INET;
                a._port = IPAddress.HostToNetworkOrder(port);
                a._addr0 = addr[0];
                a._addr1 = addr[1];
                a._addr2 = addr[2];
                a._addr3 = addr[3];
                a._nothing = 0;

                _addr = a;
            }
            else if (source.AddressFamily == AddressFamily.InterNetworkV6)
            {
                SOCKADDR_IN6 a;
                Byte[] addr = source.GetAddressBytes();
                Debug.Assert(addr.Length == 16);

                a._family = AF_INET6;
                a._port = IPAddress.HostToNetworkOrder(port);
                a._flowInfo = 0;
                a._addr0 = addr[0];
                a._addr1 = addr[1];
                a._addr2 = addr[2];
                a._addr3 = addr[3];
                a._addr4 = addr[4];
                a._addr5 = addr[5];
                a._addr6 = addr[6];
                a._addr7 = addr[7];
                a._addr8 = addr[8];
                a._addr9 = addr[9];
                a._addr10 = addr[10];
                a._addr11 = addr[11];
                a._addr12 = addr[12];
                a._addr13 = addr[13];
                a._addr14 = addr[14];
                a._addr15 = addr[15];
                a._scopeID = (Int32)source.ScopeId;

                _addr = a;
            }
            else
            {
                throw new ArgumentException();
            }

            _pinAddr = GCHandle.Alloc(_addr, GCHandleType.Pinned);
            _pAddr = _pinAddr.AddrOfPinnedObject();
        }


        void Close()
        {
            if (_pinAddr.IsAllocated)
            {
                _pinAddr.Free();
            }

            _addr = null;
            _pAddr = (IntPtr)0;
        }

        ~WinsockSockAddr()
        {
            Close();
        }

        public IntPtr PinnedSockAddr
        { get { return _pAddr; } }
    }
}
